; Low Level MIDI routines with time-stamping ; Written by Kirk Austin 5/17/87 ; This code is in the public domain and is absolutely free ; Note: Be sure and turn off range checking in LS Pascal ; to prevent a crash. ; Serial Chip equates SCCRd EQU $1D8 SCCWr EQU $1DC aData EQU 6 aCtl EQU 2 bData EQU 4 bCtl EQU 0 TBE EQU 2 ; Interrupt vector equates Lvl1DT EQU $192 Lvl2DT EQU $1B2 RxIntOffsetA EQU 24 TxIntOffsetA EQU 16 SpecRecCondA EQU 28 RxIntOffsetB EQU 8 TxIntOffsetB EQU 0 SpecRecCondB EQU 12 ; 6522 equates VIA EQU $1D4 vT1C EQU $800 vT1CH EQU $A00 vT1L EQU $C00 vACR EQU $1600 vIER EQU $1C00 ; XDEF all routines that need to be accessed externally XDEF InitSCCA XDEF InitSCCB XDEF TxMIDIA XDEF TxMIDIB XDEF RxMIDIA XDEF RxMIDIB XDEF ResetSCCA XDEF ResetSCCB XDEF InitTimer XDEF LoadTimer XDEF StartCounter XDEF GetCounter XDEF QuitTimer ; These are the routines for the Modem Port ; PROCEDURE InitSCCA; ; Call this routine at the beginning of your application if ; using the modem port for MIDI information transfers. InitSCCA MOVE SR,-(SP) ; Save interrupts MOVEM.L D0/A0-A2,-(SP) ; Save registers ORI #$0300,SR ; Disable interrupts MOVE.L SCCRd,A1 ; Get base Read address ADD #aCtl,A1 ; Add offset for control MOVE.B (A1),D0 ; Dummy read MOVE.L (SP),(SP) ; Delay MOVE.L SCCWr,A0 ; Get base Write address ADD #aCtl,A0 ; Add offset for control MOVE.B #9,(A0) ; pointer for SCC reg 9 MOVE.L (SP),(SP) ; Delay MOVE.B #%10000000,(A0) ; Reset channel MOVE.L (SP),(SP) ; Delay BSR InitSCCChan ; branch to common init routine ; set up interrupt vectors MOVE.L #Lvl2DT,A0 ; get dispatch table ptr MOVE #RxIntOffsetA,D0 ; get offset to Rx vector LEA PRxIntHandA,A1 ; point to previous vector storage MOVE.L 0(A0,D0),(A1) ; save previous interrupt vector LEA RxIntHandA,A1 ; set Rx vector MOVE.L A1,0(A0,D0) MOVE #TxIntOffsetA,D0 ; get offset to Tx vector LEA PTxIntHandA,A1 ; point to previous vector storage MOVE.L 0(A0,D0),A1 ; save previous interrupt vector LEA TxIntHandA,A1 ; set Tx vector MOVE.L A1,0(A0,D0) MOVE #SpecRecCondA,D0 ; offset to Special vector LEA StubA,A1 MOVE.L A1,0(A0,D0) ; initialize the flags & pointers LEA RxByteInA,A2 ; get the address CLR (A2) LEA RxByteOutA,A2 ; get the address CLR (A2) LEA RxQEmptyA,A2 ; get the address MOVE #$FFFF,(A2) LEA TxByteInA,A2 ; get the address CLR (A2) LEA TxQEmptyA,A2 ; get the address MOVE #$FFFF,(A2) MOVEM.L (SP)+,D0/A0-A2 ; Restore registers MOVE (SP)+,SR ; Restore interrupts RTS ; and return ; This is the common initialization routine for both channels InitSCCChan MOVE.B #4,(A0) ; pointer for SCC reg 4 MOVE.L (SP),(SP) ; Delay MOVE.B #%10000100,(A0) ; 32x clock, 1 stop bit MOVE.L (SP),(SP) ; Delay MOVE.B #1,(A0) ; pointer for SCC reg 1 MOVE.L (SP),(SP) ; Delay MOVE.B #%00000000,(A0) ; No W/Req MOVE.L (SP),(SP) ; Delay MOVE.B #3,(A0) ; pointer for SCC reg 3 MOVE.L (SP),(SP) ; Delay MOVE.B #%00000000,(A0) ; Turn off Rx MOVE.L (SP),(SP) ; Delay MOVE.B #5,(A0) ; pointer for SCC reg 5 MOVE.L (SP),(SP) ; Delay MOVE.B #%00000000,(A0) ; Turn off Tx MOVE.L (SP),(SP) ; Delay MOVE.B #11,(A0) ; pointer for SCC reg 11 MOVE.L (SP),(SP) ; Delay MOVE.B #%00101000,(A0) ; Make TRxC clock source MOVE.L (SP),(SP) ; Delay MOVE.B #14,(A0) ; pointer for SCC reg 14 MOVE.L (SP),(SP) ; Delay MOVE.B #%00000000,(A0) ; Disable BRGen MOVE.L (SP),(SP) ; Delay MOVE.B #3,(A0) ; pointer for SCC reg 3 MOVE.L (SP),(SP) ; Delay MOVE.B #%11000001,(A0) ; Enable Rx MOVE.L (SP),(SP) ; Delay MOVE.B #5,(A0) ; pointer for SCC reg 5 MOVE.L (SP),(SP) ; Delay MOVE.B #%01101010,(A0) ; Enable Tx and drivers MOVE.L (SP),(SP) ; Delay MOVE.B #15,(A0) ; pointer for SCC reg 15 MOVE.L (SP),(SP) ; Delay MOVE.B #%00001000,(A0) ; Enable DCD int for mouse MOVE.L (SP),(SP) ; Delay MOVE.B #0,(A0) ; pointer for SCC reg 0 MOVE.L (SP),(SP) ; Delay MOVE.B #%00010000,(A0) ; Reset EXT/STATUS MOVE.L (SP),(SP) ; Delay MOVE.B #0,(A0) ; pointer for SCC reg 0 MOVE.L (SP),(SP) ; Delay MOVE.B #%00010000,(A0) ; Reset EXT/STATUS again MOVE.L (SP),(SP) ; Delay MOVE.B #1,(A0) ; pointer for SCC reg 1 MOVE.L (SP),(SP) ; Delay MOVE.B #%00010011,(A0) ; Enable interrupts MOVE.L (SP),(SP) ; Delay MOVE.B #9,(A0) ; pointer for SCC reg 9 MOVE.L (SP),(SP) ; Delay MOVE.B #%00001010,(A0) ; Set master int enable MOVE.L (SP),(SP) ; Delay RTS ; PROCEDURE TxMIDIA (TheData : integer); ; This is the routine to transmit a MIDI byte of data ; through the Modem Port. To use this routine place ; the byte to be transmitted as the lower 8 bits ; of a word on the stack, then call TxMIDIA. TxMIDIA LINK A6,#0 ; set frame pointer MOVE SR,-(SP) ; Save interrupts MOVEM.L D0/A0-A3,-(SP) ; Save registers ORI #$0300,SR ; Disable interrupts LEA TxQEmptyA,A3 ; get the address TST.B (A3) ; is TxQueue empty? BNE TxQEA ; if so branch LEA TxByteInA,A3 ; get the address MOVE (A3),D0 ; if not add byte to queue LEA TxQueueA,A2 ; point to queue MOVE.B 9(A6),0(A2,D0) ; place byte in queue ADDQ #1,D0 ; update TxByteIn CMP #$100,D0 BNE @1 MOVE #0,D0 @1 MOVE D0,(A3) BRA TxExitA ; and exit TxQEA MOVE.L SCCRd,A0 ; get SCC Read Address MOVE.L SCCWr,A1 ; get SCC Write Address MOVE #aCtl,D0 ; get index for Ctl BTST.B #TBE,0(A0,D0) ; transmit buffer empty? BNE FirstByteA ; if so branch LEA TxByteInA,A3 ; get the address MOVE (A3),D0 ; if not add to queue LEA TxQueueA,A2 ; point to queue MOVE.B 9(A6),0(A2,D0) ; place byte in queue ADDQ #1,D0 ; update pointer CMP #$100,D0 BNE @1 MOVE #0,D0 @1 MOVE D0,(A3) LEA TxQEmptyA,A3 ; get the address MOVE #0,(A3) ; reset queue empty flag BRA TxExitA ; and exit FirstByteA MOVE #aData,D0 ; get index to data MOVE.L (SP),(SP) ; Delay MOVE.B 9(A6),0(A1,D0) ; write data to SCC MOVE.L (SP),(SP) ; Delay TxExitA MOVEM.L (SP)+,D0/A0-A3 ; Restore registers MOVE (SP)+,SR ; Restore interrupts UNLK A6 ; release frame pointer MOVE.L (SP)+,A1 ; save return address ADD.L #2,SP ; move past data word MOVE.L A1,-(SP) ; put address back on stack RTS ; and return ; Function RxMIDIA : LongInt; ; This routine gets a byte through the modem port. ; To use this routine treat it like a Pascal ; function. Leave space on the stack for a longword ; of data before calling this routine. If the data ; on the stack after ; the routine executes is 0 there was no MIDI data available. ; If it's non-0 the upper 3 bytes contain the counter ; value, the MIDI byte is the low byte. RxMIDIA LINK A6,#0 ; set frame pointer MOVE SR,-(SP) ; save interrupts MOVEM.L D0-D1/A0-A3,-(SP) ; Save registers ORI #$0300,SR ; disable interrupts LEA RxQEmptyA,A3 ; get the address TST.B (A3) ; any data available? BEQ @1 ; if so, branch MOVE.L #0,8(A6) ; if not, return with 0 BRA RxExitA @1 LEA RxByteOutA,A3 ; get the address MOVE (A3),D0 ; get index to byte out LEA RxQueueA,A2 ; point to queue MOVE.L #0,D1 ; clear data register MOVE.L 0(A2,D0),D1 ; get MIDI data MOVE.L D1,8(A6) ; place on stack for return ADDQ #4,D0 ; update index CMP #$400,D0 BNE @2 MOVE #0,D0 @2 LEA RxByteOutA,A3 ; get the address MOVE D0,(A3) LEA RxByteInA,A3 ; get the address MOVE (A3),D1 CMP D0,D1 ; is queue empty? BNE RxExitA ; if not exit LEA RxQEmptyA,A3 ; get the address MOVE #$FFFF,(A3) ; if empty, set flag RxExitA MOVEM.L (SP)+,D0-D1/A0-A3 ; Restore registers MOVE (SP)+,SR ; restore interrupts UNLK A6 RTS ; and return ; This is the interrupt routine for receiving through ; the modem port. It places the counter value and the ; MIDI byte in a circular queue to be ; accessed later by the application. ; When the system gets this far, A0 contains the ; SCC base read Ctl address ; and A1 contains the SCC base write Ctl address ; for this channel. The data addresses are offset by 4 ; from the control addresses. ; D0-D3/A0-A3 are already preserved, so they may ; be used freely. RxIntHandA ORI #$0300,SR ; disable interrupts @3 MOVE #4,D0 ; get data offset CLR.L D1 ; prepare for data MOVE.L (SP),(SP) ; Delay MOVE.B 0(A0,D0),D1 ; read data from SCC MOVE.L (SP),(SP) ; Delay LEA RxQueueA,A2 ; point to queue LEA RxByteInA,A3 ; get the address MOVE (A3),D0 ; get offset to next cell LEA Counter,A3 ; get the address MOVE.L (A3),D2 ; put counter value in D2 LSL.L #8,D2 ; shift counter one byte ADD.L D2,D1 ; combine counter and data MOVE.L D1,0(A2,D0) ; put longword in queue LEA RxQEmptyA,A3 ; get the address MOVE #0,(A3) ; reset queue empty flag ADDQ #4,D0 ; update index CMP #$400,D0 BNE @1 MOVE #0,D0 @1 LEA RxByteInA,A3 ; get the address MOVE D0,(A3) @2 BTST.B #0,(A0) ; is there more data? BNE @3 ; do it again if there is ANDI #$F8FF,SR ; enable interrupts RTS ; and return ; This is the interrupt routine for transmitting a byte ; through the modem port. It checks to see if there ; is any data to send, and if there is it sends it to ; the SCC. If there isn't it resets the TBE interrupt ; in the SCC and exits. ; When the system gets this far, A0 contains the SCC ; base read Ctl address and A1 contains the SCC base ; write Ctl address for this channel. ; The data addresses are offset by 4 from the control ; addresses. D0-D3/A0-A3 are already preserved, so ; they may be used freely. TxIntHandA ORI #$0300,SR ; disable interrupts LEA TxQEmptyA,A3 ; get the address TST.B (A3) ; Is queue empty? BEQ @1 ; if not branch MOVE.B #$28,(A1) ; if so, reset TBE interrupt MOVE.L (SP),(SP) ; Delay BRA TxIExitA ; and exit @1 LEA TxByteOutA,A3 ; get the address MOVE (A3),D0 ; get index to next data byte LEA TxQueueA,A2 ; point to queue MOVE #4,D1 ; get data offset MOVE.B 0(A2,D0),0(A1,D1) ; write data to SCC MOVE.L (SP),(SP) ; Delay ADDQ #1,D0 ; update index CMP #$100,D0 BNE @2 MOVE #0,D0 @2 LEA TxByteOutA,A3 ; get the address MOVE D0,(A3) LEA TxByteInA,A3 ; get the address MOVE (A3),D1 CMP D0,D1 ; is TxQueue empty? BNE TxIExitA ; if not exit LEA TxQEmptyA,A3 ; get the address MOVE #$FFFF,(A3) ; if empty set flag TxIExitA ANDI #$F8FF,SR ; enable interrupts RTS ; and return ; PROCEDURE ResetSCCA; ; If you called InitSCCA at the beginning of your ; application this ; routine must be called when the application ; quits or the system will ; crash due to the interrupt handling pointers ; becoming invalid. ResetSCCA MOVEM.L D0/A0-A1,-(SP) ; save registers MOVE SR,-(SP) ; Save interrupts ORI #$0300,SR ; Disable interrupts MOVE.L SCCWr,A0 ; Get base Write address ADD #aCtl,A0 ; Add offset for control MOVE.B #9,(A0) ; pointer for SCC reg 9 MOVE.L (SP),(SP) ; Delay MOVE.B #%10000000,(A0) ; Reset channel MOVE.L (SP),(SP) ; Delay BSR ResetSCCChan ; branch to common reset routine MOVE.L #Lvl2DT,A0 ; get dispatch table pointer MOVE #RxIntOffsetA,D0 ; get offset to Rx vector LEA PRxIntHandA,A1 ; point to previous vector storage MOVE.L (A1),0(A0,D0) ; restore previous int vector MOVE #TxIntOffsetA,D0 ; get offset to Tx vector LEA PTxIntHandA,A1 ; set Rx vector MOVE.L (A1),0(A0,D0) ; restore previous int vector MOVE (SP)+,SR ; Restore interrupts MOVEM.L (SP)+,D0/A0-A1 ; restore registers RTS ; and return ; This is the common reset routine for both channels ResetSCCChan MOVE.B #15,(A0) ; pointer for SCC reg 15 MOVE.L (SP),(SP) ; Delay MOVE.B #%00001000,(A0) ; Enable DCD int MOVE.L (SP),(SP) ; Delay MOVE.B #0,(A0) ; pointer for SCC reg 0 MOVE.L (SP),(SP) ; Delay MOVE.B #%00010000,(A0) ; Reset EXT/STATUS MOVE.L (SP),(SP) ; Delay MOVE.B #0,(A0) ; pointer for SCC reg 0 MOVE.L (SP),(SP) ; Delay MOVE.B #%00010000,(A0) ; Reset EXT/STATUS again MOVE.L (SP),(SP) ; Delay MOVE.B #1,(A0) ; pointer for SCC reg 1 MOVE.L (SP),(SP) ; Delay MOVE.B #%00000001,(A0) ; Enable mouse interrupts MOVE.L (SP),(SP) ; Delay MOVE.B #9,(A0) ; pointer for SCC reg 9 MOVE.L (SP),(SP) ; Delay MOVE.B #%00001010,(A0) ; Set master int enable MOVE.L (SP),(SP) ; Delay RTS TxQueueA DCB.B $100,0 ; this is the queue TxQEmptyA DC 0 ; the queue empty flag TxByteInA DC 0 ; index to next cell in TxByteOutA DC 0 ; index to next cell out RxQueueA DCB.B $400,0 ; this is the queue RxQEmptyA DC 0 ; the empty queue flag RxByteInA DC 0 ; index to next cell in RxByteOutA DC 0 ; index to next cell out PRxIntHandA DC 0 ; Previous interrupt vector PTxIntHandA DC 0 ; Previous interrupt vector ; These are the routines for the Printer Port ; PROCEDURE InitSCCB; ; Call this routine at the beginning of your application if ; using the printer port for MIDI information transfers. InitSCCB MOVE SR,-(SP) ; Save interrupts MOVEM.L D0/A0-A2,-(SP) ; Save registers ORI #$0300,SR ; Disable interrupts MOVE.L SCCRd,A1 ; Get base Read address ADD #bCtl,A1 ; Add offset for control MOVE.B (A1),D0 ; Dummy read MOVE.L (SP),(SP) ; Delay MOVE.L SCCWr,A0 ; Get base Write address ADD #bCtl,A0 ; Add offset for control MOVE.B #9,(A0) ; pointer for SCC reg 9 MOVE.L (SP),(SP) ; Delay MOVE.B #%01000000,(A0) ; Reset channel MOVE.L (SP),(SP) ; Delay BSR InitSCCChan ; branch to common init routine ; set up interrupt vectors MOVE.L #Lvl2DT,A0 ; get dispatch table ptr MOVE #RxIntOffsetB,D0 ; get offset to Rx vector LEA PRxIntHandB,A1 ; point to previous vector storage MOVE.L 0(A0,D0),(A1) ; save previous interrupt vector LEA RxIntHandB,A1 ; set Rx vector MOVE.L A1,0(A0,D0) MOVE #TxIntOffsetB,D0 ; get offset to Tx vector LEA PTxIntHandB,A1 ; point to previous vector storage MOVE.L 0(A0,D0),A1 ; save previous interrupt vector LEA TxIntHandB,A1 ; set Tx vector MOVE.L A1,0(A0,D0) MOVE #SpecRecCondB,D0 ; offset to Special vector LEA StubB,A1 MOVE.L A1,0(A0,D0) ; initialize the flags & pointers LEA RxByteInB,A2 ; get the address CLR (A2) LEA RxByteOutB,A2 ; get the address CLR (A2) LEA RxQEmptyB,A2 ; get the address MOVE #$FFFF,(A2) LEA TxByteInB,A2 ; get the address CLR (A2) LEA TxQEmptyB,A2 ; get the address MOVE #$FFFF,(A2) MOVEM.L (SP)+,D0/A0-A2 ; Restore registers MOVE (SP)+,SR ; Restore interrupts RTS ; and return ; PROCEDURE TxMIDIB (TheData : integer); ; This is the routine to transmit a MIDI byte of data ; through the Printer Port. To use this routine place ; the byte to be transmitted as the lower 8 bits ; of a word on the stack, then call TxMIDIB. TxMIDIB LINK A6,#0 ; set frame pointer MOVE SR,-(SP) ; Save interrupts MOVEM.L D0/A0-A3,-(SP) ; Save registers ORI #$0300,SR ; Disable interrupts LEA TxQEmptyB,A3 ; get the address TST.B (A3) ; is TxQueue empty? BNE TxQEB ; if so branch LEA TxByteInB,A3 ; get the address MOVE (A3),D0 ; if not add byte to queue LEA TxQueueB,A2 ; point to queue MOVE.B 9(A6),0(A2,D0) ; place byte in queue ADDQ #1,D0 ; update TxByteIn CMP #$100,D0 BNE @1 MOVE #0,D0 @1 MOVE D0,(A3) BRA TxExitB ; and exit TxQEB MOVE.L SCCRd,A0 ; get SCC Read Address MOVE.L SCCWr,A1 ; get SCC Write Address MOVE #bCtl,D0 ; get index for Ctl BTST.B #TBE,0(A0,D0) ; transmit buffer empty? BNE FirstByteB ; if so branch LEA TxByteInB,A3 ; get the address MOVE (A3),D0 ; if not add to queue LEA TxQueueB,A2 ; point to queue MOVE.B 9(A6),0(A2,D0) ; place byte in queue ADDQ #1,D0 ; update pointer CMP #$100,D0 BNE @1 MOVE #0,D0 @1 MOVE D0,(A3) LEA TxQEmptyB,A3 ; get the address MOVE #0,(A3) ; reset queue empty flag BRA TxExitB ; and exit FirstByteB MOVE #bData,D0 ; get index to data MOVE.L (SP),(SP) ; Delay MOVE.B 9(A6),0(A1,D0) ; write data to SCC MOVE.L (SP),(SP) ; Delay TxExitB MOVEM.L (SP)+,D0/A0-A3 ; Restore registers MOVE (SP)+,SR ; Restore interrupts UNLK A6 ; release frame pointer MOVE.L (SP)+,A1 ; save return address ADD.L #2,SP ; move past data word MOVE.L A1,-(SP) ; put address back on stack RTS ; and return ; Function RxMIDIB : LongInt; ; This routine gets a byte through the printer port. ; To use this routine treat it like a Pascal ; function. Leave space on the stack for a longword ; of data before calling this routine. If the data ; on the stack after ; the routine executes is 0 there was no MIDI data available. ; If it's non-0 the upper 3 bytes contain the counter ; value, the MIDI byte is the low byte. RxMIDIB LINK A6,#0 ; set frame pointer MOVE SR,-(SP) ; save interrupts MOVEM.L D0-D1/A0-A3,-(SP) ; Save registers ORI #$0300,SR ; disable interrupts LEA RxQEmptyB,A3 ; get the address TST.B (A3) ; any data available? BEQ @1 ; if so, branch MOVE.L #0,8(A6) ; if not, return with 0 BRA RxExitB @1 LEA RxByteOutB,A3 ; get the address MOVE (A3),D0 ; get index to byte out LEA RxQueueB,A2 ; point to queue MOVE.L #0,D1 ; clear data register MOVE.L 0(A2,D0),D1 ; get MIDI data MOVE.L D1,8(A6) ; place on stack for return ADDQ #4,D0 ; update index CMP #$400,D0 BNE @2 MOVE #0,D0 @2 LEA RxByteOutB,A3 ; get the address MOVE D0,(A3) LEA RxByteInB,A3 ; get the address MOVE (A3),D1 CMP D0,D1 ; is queue empty? BNE RxExitB ; if not exit LEA RxQEmptyB,A3 ; get the address MOVE #$FFFF,(A3) ; if empty, set flag RxExitB MOVEM.L (SP)+,D0-D1/A0-A3 ; Restore registers MOVE (SP)+,SR ; restore interrupts UNLK A6 RTS ; and return ; This is the interrupt routine for receiving through ; the printer port. It places the counter value and the ; MIDI byte in a circular queue to be ; accessed later by the application. ; When the system gets this far, A0 contains the ; SCC base read Ctl address ; and A1 contains the SCC base write Ctl address ; for this channel. The data addresses are offset by 4 ; from the control addresses. ; D0-D3/A0-A3 are already preserved, so they may ; be used freely. RxIntHandB ORI #$0300,SR ; disable interrupts @3 MOVE #4,D0 ; get data offset CLR.L D1 ; prepare for data MOVE.L (SP),(SP) ; Delay MOVE.B 0(A0,D0),D1 ; read data from SCC MOVE.L (SP),(SP) ; Delay LEA RxQueueB,A2 ; point to queue LEA RxByteInB,A3 ; get the address MOVE (A3),D0 ; get offset to next cell LEA Counter,A3 ; get the address MOVE.L (A3),D2 ; put counter value in D2 LSL.L #8,D2 ; shift counter one byte ADD.L D2,D1 ; combine counter and data MOVE.L D1,0(A2,D0) ; put longword in queue LEA RxQEmptyB,A3 ; get the address MOVE #0,(A3) ; reset queue empty flag ADDQ #4,D0 ; update index CMP #$400,D0 BNE @1 MOVE #0,D0 @1 LEA RxByteInB,A3 ; get the address MOVE D0,(A3) @2 BTST.B #0,(A0) ; is there more data? BNE @3 ; do it again if there is ANDI #$F8FF,SR ; enable interrupts RTS ; and return ; This is the interrupt routine for transmitting a byte ; through the printer port. It checks to see if there ; is any data to send, and if there is it sends it to ; the SCC. If there isn't it resets the TBE interrupt ; in the SCC and exits. ; When the system gets this far, A0 contains the SCC ; base read Ctl address and A1 contains the SCC base ; write Ctl address for this channel. ; The data addresses are offset by 4 from the control ; addresses. D0-D3/A0-A3 are already preserved, so ; they may be used freely. TxIntHandB ORI #$0300,SR ; disable interrupts LEA TxQEmptyB,A3 ; get the address TST.B (A3) ; Is queue empty? BEQ @1 ; if not branch MOVE.B #$28,(A1) ; if so, reset TBE interrupt MOVE.L (SP),(SP) ; Delay BRA TxIExitB ; and exit @1 LEA TxByteOutB,A3 ; get the address MOVE (A3),D0 ; get index to next data byte LEA TxQueueB,A2 ; point to queue MOVE #4,D1 ; get data offset MOVE.B 0(A2,D0),0(A1,D1) ; write data to SCC MOVE.L (SP),(SP) ; Delay ADDQ #1,D0 ; update index CMP #$100,D0 BNE @2 MOVE #0,D0 @2 LEA TxByteOutB,A3 ; get the address MOVE D0,(A3) LEA TxByteInB,A3 ; get the address MOVE (A3),D1 CMP D0,D1 ; is TxQueue empty? BNE TxIExitB ; if not exit LEA TxQEmptyB,A3 ; get the address MOVE #$FFFF,(A3) ; if empty set flag TxIExitB ANDI #$F8FF,SR ; enable interrupts RTS ; and return ; PROCEDURE ResetSCCB; ; If you called InitSCCB at the beginning of your ; application this ; routine must be called when the application ; quits or the system will ; crash due to the interrupt handling pointers ; becoming invalid. ResetSCCB MOVEM.L D0/A0-A1,-(SP) ; save registers MOVE SR,-(SP) ; Save interrupts ORI #$0300,SR ; Disable interrupts MOVE.L SCCWr,A0 ; Get base Write address ADD #bCtl,A0 ; Add offset for control MOVE.B #9,(A0) ; pointer for SCC reg 9 MOVE.L (SP),(SP) ; Delay MOVE.B #%01000000,(A0) ; Reset channel MOVE.L (SP),(SP) ; Delay BSR ResetSCCChan ; branch to common reset routine MOVE.L #Lvl2DT,A0 ; get dispatch table pointer MOVE #RxIntOffsetB,D0 ; get offset to Rx vector LEA PRxIntHandB,A1 ; point to previous vector storage MOVE.L (A1),0(A0,D0) ; restore previous int vector MOVE #TxIntOffsetB,D0 ; get offset to Tx vector LEA PTxIntHandB,A1 ; set Rx vector MOVE.L (A1),0(A0,D0) ; restore previous int vector MOVE (SP)+,SR ; Restore interrupts MOVEM.L (SP)+,D0/A0-A1 ; restore registers RTS ; and return TxQueueB DCB.B $100,0 ; this is the queue TxQEmptyB DC 0 ; the queue empty flag TxByteInB DC 0 ; index to next cell in TxByteOutB DC 0 ; index to next cell out RxQueueB DCB.B $400,0 ; this is the queue RxQEmptyB DC 0 ; the empty queue flag RxByteInB DC 0 ; index to next cell in RxByteOutB DC 0 ; index to next cell out PRxIntHandB DC 0 ; Previous interrupt vector PTxIntHandB DC 0 ; Previous interrupt vector ; This is the space for a special condition interrupt ; routine. All I do here is reset the error flag in the SCC ; and return. When the system gets this far, A0 contains ; the SCC base read Ctl address ; and A1 contains the SCC base write Ctl address ; for this channel. ; The data addresses are offset by 4 from the control ; addresses. D0-D3/A0-A3 are already preserved, so ; they may be used freely. StubA ORI #$0300,SR ; Disable interrupts MOVE.B #%00110000,(A1) ; Reset Error MOVE.L (SP),(SP) ; Delay ANDI #$F8FF,SR ; Restore interrupts RTS ; This is the space for a special condition interrupt ; routine. All I do here is reset the error flag in the SCC ; and return. When the system gets this far, A0 contains ; the SCC base read Ctl address ; and A1 contains the SCC base write Ctl address ; for this channel. ; The data addresses are offset by 4 from the control ; addresses. D0-D3/A0-A3 are already preserved, so ; they may be used freely. StubB ORI #$0300,SR ; Disable interrupts MOVE.B #%00110000,(A1) ; Reset Error MOVE.L (SP),(SP) ; Delay ANDI #$F8FF,SR ; Restore interrupts RTS ; These are the routines for the counter you can use for ; time-stamping the incoming MIDI data. This is useful ; for writing sequencer type applications. ; The time-stamping is done on an iterrupt level, ; is extremely accurate, ; and uses the VIA timer #1. This means that you can't ; use any of the Sound Manager routines because they use ; timer #1 too. If you want to create a metronome click ; you have to write your own code that accesses ; the sound hardware directly without using timer #1. ; InitTimer and LoadTimer expect a word on the stack ; to load the timer. ; To increment the counter every millisecond, load the ; timer with decimal 782. If you aren't going to use ; time-stamping you can ignore these routines. ; PROCEDURE InitTimer ( TimrValue : integer); ; Only call InitTimer once at the beginning ; of your application 1 millisecond is decimal 782. InitTimer LINK A6,#0 ; set frame pointer MOVEM.L D0/A0-A1,-(SP) MOVE.L #Lvl1DT,A0 ; Point to level 1 dispatch table LEA PrevIVC,A1 ; Point to interrupt vector storage MOVE.L 24(A0),(A1) ; save previous interrupt vector LEA CounterIntHand,A1 ; point to new interrupt handler MOVE.L A1,24(A0) ; put it in the dispatch table MOVE.L VIA,A1 ; point to the 6522 chip ORI.B #$40,vACR(A1) ; set the timer to freerun mode MOVE.B #$C0,vIER(A1) ; Enable timer interrupts MOVE 8(A6),D0 ; Get timer value MOVE.B D0,vT1L(A1) ; set timer lo byte LSR #8, D0 ; shift to hi byte MOVE.B D0,vT1CH(A1) ; set timer hi byte MOVEM.L (SP)+,D0/A0-A1 UNLK A6 MOVE.L (SP)+,A0 ; save return address ADDQ #2,SP ; move past timer value MOVE.L A0,-(SP) ; replace return address RTS ; PROCEDURE LoadTimer (TimrValue : integer); ; Call LoadTimer whenever you want to change the timer value. ; 1 millisecond is decimal 782. LoadTimer LINK A6,#0 ; set frame pointer MOVEM.L D0/A0-A1,-(SP) MOVE.L VIA,A1 ; point to the 6522 chip MOVE 8(A6),D0 ; Get timer value MOVE.B D0,vT1L(A1) ; set timer lo byte LSR #8,D0 ; shift to hi byte MOVE.B D0,vT1CH(A1) ; set timer hi byte MOVEM.L (SP)+,D0/A0-A1 UNLK A6 MOVE.L (SP)+,A0 ; save return address ADDQ #2,SP ; move past timer value MOVE.L A0,-(SP) ; replace return address RTS ; PROCEDURE StartCounter; ; StartCounter sets the counter value to 1 StartCounter LEA Counter,A0 ; point to the counter MOVE.L #1,(A0) ; set it to 1 RTS ; FUNCTION GetCounter : LongInt; ; GetCounter returns a longword that is the value ; of the counter GetCounter MOVE.L A0,-(SP) LEA Counter,A0 ; point to the counter MOVE.L (A0),8(SP) ; return it as function result MOVE.L (SP)+,A0 RTS ; PROCEDURE QuitTimer; ; Call QuitTimer when your application is done or the system will crash. QuitTimer MOVEM.L A0-A1,-(SP) MOVE.L VIA,A1 ; Disable 6522 interrupts MOVE.B #$40,vIER(A1) LEA PrevIVC,A1 ; Restore previous interrupt vector MOVE.L #Lvl1DT,A0 MOVE.L (A1),24(A0) MOVEM.L (SP)+,A0-A1 RTS ; This is the interrupt handler routine for the counter. ; When the system gets this far A1 contains the base ; address of the VIA. ; It also preserves D0-D3/A0-A3. CounterIntHand LEA Counter,A0 ; point to the counter ADDQ.L #1,(A0) ; Increment it MOVE.B vT1C(A1),D0 ; Clear interrupt flag on 6522 RTS Counter DC.L 1 ; The counter PrevIVC DC.L 0 ; Previous interrupt vector END